
#include <BOpenGL>
#include <BRender>

#include <BGeometry>
#include "member_BGeometry.h"

using namespace BWE;

#define member					(*(member_BGeometry*)_ptr)
#define member_allocate()		_ptr = new member_BGeometry(this)
#define member_release()		delete (member_BGeometry*)_ptr

BGeometry::BGeometry()
{
	member_allocate();
}
BGeometry::BGeometry(const BString& name)
{
	member_allocate();
	this->setName(name);
}
BGeometry::~BGeometry()
{
	member_release();
}

void BGeometry::setVertices(BArray<BVector>* vertices)
{
	member.vertices = vertices;
	this->dirty();
}
BArray<BVector>* BGeometry::vertices()
{
	return member.vertices.ptr();
}
const BArray<BVector>* BGeometry::vertices() const
{
	return member.vertices.ptr();
}

void BGeometry::setNormals(BArray<BVector>* normals)
{
	member.normals = normals;
}
BArray<BVector>* BGeometry::normals()
{
	return member.normals.ptr();
}
const BArray<BVector>* BGeometry::normals() const
{
	return member.normals.ptr();
}

void BGeometry::setColors(BColorArray* colors)
{
	member.colors = colors;
}
BColorArray* BGeometry::colors()
{
	return member.colors.ptr();
}
const BColorArray* BGeometry::colors() const
{
	return member.colors.ptr();
}

void BGeometry::setTexCoords(BCoordArray* texCoords, int unit)
{
	if (unit >= 0 && unit < 8)
	{
		member.coords[unit] = texCoords;
	}
}
BCoordArray* BGeometry::texCoords(int unit)
{
	if (unit >= 0 && unit < 8)
	{
		return member.coords[unit];
	}
	return 0;
}
const BCoordArray* BGeometry::texCoords(int unit) const
{
	if (unit >= 0 && unit < 8)
	{
		return member.coords[unit];
	}
	return 0;
}

void BGeometry::addElement(BElement* element)
{
	if (element && !member.elements.contain(element))
	{
		member.elements.append(element);
	}
}
void BGeometry::addElement(Primitive primitive, int first, int count)
{
	BElement* element = new BElement(primitive, first, count);
	addElement(element);
}

void BGeometry::removeElement(const BElement* element)
{
	member.elements.remove(element);
}
void BGeometry::clearElements()
{
	member.elements.clear();
}

int BGeometry::elementCount() const
{
	return member.elements.size();
}
BElement* BGeometry::element(int index)
{
	return member.elements(index);
}
const BElement* BGeometry::element(int index) const
{
	return member.elements(index);
}

void BGeometry::clear()
{
	member.vertices = 0;
	member.normals = 0;
	member.colors = 0;
	member.elements.clear();
	for (int i = 0; i < 8; i++)
	{
		member.coords[i] = 0;
	}
	this->dirty();
}
bool BGeometry::empty() const
{
	return member.vertices == 0;
}
void BGeometry::smooth()
{
	BVectorArray* vertices = member.vertices;
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != vertices->size())
	{
		normals = new BVectorArray(vertices->size());
		this->setNormals(normals);
	}
	normals->fill(BVector());
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		int first = element->first();
		int count = element->count();
		BIndexArray* indices = element->indices();
		if (indices)
		{
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < count; i += 3)
				{
					unsigned int ia = (*indices)[first + i];
					unsigned int ib = (*indices)[first + i + 1];
					unsigned int ic = (*indices)[first + i + 2];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < count; i += 4)
				{
					unsigned int ia = (*indices)[first + i];
					unsigned int ib = (*indices)[first + i + 1];
					unsigned int ic = (*indices)[first + i + 2];
					unsigned int id = (*indices)[first + i + 3];
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
					(*normals)[id] += normal;
				}
			}
		}
		else
		{
			if (element->primitive() == Primitive_Triangles)
			{
				for (int i = 0; i < count; i += 3)
				{
					unsigned int ia = first + i;
					unsigned int ib = first + i + 1;
					unsigned int ic = first + i + 2;
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
				}
			}
			if (element->primitive() == Primitive_Quads)
			{
				for (int i = 0; i < count; i += 4)
				{
					unsigned int ia = first + i;
					unsigned int ib = first + i + 1;
					unsigned int ic = first + i + 2;
					unsigned int id = first + i + 3;
					const BVector& va = (*vertices)[ia];
					const BVector& vb = (*vertices)[ib];
					const BVector& vc = (*vertices)[ic];
					BVector normal = (vb - va).cross(vc - vb);
					normal.normalize();
					(*normals)[ia] += normal;
					(*normals)[ib] += normal;
					(*normals)[ic] += normal;
					(*normals)[id] += normal;
				}
			}
		}
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i].normalize();
	}
	this->setNormals(normals);
}
void BGeometry::reversal()
{
	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		int first = element->first();
		int count = element->count();
		BIndexArray* indices = element->indices();
		if (element->primitive() == Primitive_Triangles)
		{
			for (int i = 0; i < count; i += 3)
			{
				unsigned int ia = (*indices)[first + i];
				unsigned int ib = (*indices)[first + i + 1];
				(*indices)[first + i] = ib;
				(*indices)[first + i + 1] = ia;
			}
		}
		if (element->primitive() == Primitive_Quads)
		{
			for (int i = 0; i < count; i += 4)
			{
				unsigned int ia = (*indices)[first + i];
				unsigned int ib = (*indices)[first + i + 1];
				unsigned int ic = (*indices)[first + i + 2];
				unsigned int id = (*indices)[first + i + 3];
				(*indices)[first + i] = id;
				(*indices)[first + i + 1] = ic;
				(*indices)[first + i + 2] = ib;
				(*indices)[first + i + 3] = ia;
			}
		}
	}
	BVectorArray* normals = member.normals;
	if (!normals || normals->size() != this->vertices()->size())
	{
		smooth();
		return;
	}
	for (int i = 0; i < normals->size(); i++)
	{
		(*normals)[i] = -(*normals)[i];
	}
}

void BGeometry::refresh()
{
	BSpace space;
	if (member.vertices && member.vertices->size())
	{
		space.reset(member.vertices->first());
		for (int i = 1; i < member.vertices->size(); i++)
		{
			space.expand((*member.vertices.ptr())[i]);
		}
	}
	this->setSpace(space);
}
void BGeometry::render(BRender& render)
{
	if(!member.vertices || member.vertices->empty() || member.elements.empty())
		return;

	if (member.vertices)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glVertexPointer(3, GL_FLOAT, 0, member.vertices->data());
	}

	if (member.normals)
	{
		glEnableClientState(GL_NORMAL_ARRAY);
		glNormalPointer(GL_FLOAT, 0, member.normals->data());
	}

	if (member.colors)
	{
		glEnableClientState(GL_COLOR_ARRAY);
		glColorPointer(4, GL_UNSIGNED_BYTE, 0, member.colors->data());
	}

	if (member.coords[0] && this->texture(0))
	{
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glTexCoordPointer(2, GL_FLOAT, 0, member.coords[0]->data());
	}

	for (int i = 0; i < member.elements.size(); i++)
	{
		BElement* element = member.elements[i];
		GLenum mode = 0;
		switch (element->primitive())
		{
		case Primitive_Points:			mode = GL_POINTS;			break;
		case Primitive_Lines:			mode = GL_LINES;			break;
		case Primitive_LineStrip:		mode = GL_LINE_STRIP;		break;
		case Primitive_LineLoop:		mode = GL_LINE_LOOP;		break;
		case Primitive_Triangles:		mode = GL_TRIANGLES;		break;
		case Primitive_TriangleStrip:	mode = GL_TRIANGLE_STRIP;	break;
		case Primitive_TriangleFan:		mode = GL_TRIANGLE_FAN;		break;
		case Primitive_Quads:			mode = GL_QUADS; 			break;
		case Primitive_QuadStrip:		mode = GL_QUAD_STRIP;		break;
		case Primitive_Polygon:			mode = GL_POLYGON;			break;
		default:
			continue;
		}
		BIndexArray* indices = element->indices();
		if (indices)
		{
			glEnableClientState(GL_INDEX_ARRAY);
			glDrawElements(mode, indices->size(), GL_UNSIGNED_INT, indices->data());
			glDisableClientState(GL_INDEX_ARRAY);
		}
		else
		{
			int first = element->first();
			int count = element->count();
			glDrawArrays(mode, first, count);
		}
	}

	if (member.vertices)
	{
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	if (member.normals)
	{
		glDisableClientState(GL_NORMAL_ARRAY);
	}
	if (member.colors)
	{
		glDisableClientState(GL_COLOR_ARRAY);
	}
	if (member.coords[0] && this->texture())
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}
}


